/*****************************************************************************
 * Copyright (C) Stacy Curl. All rights reserved.                            *
 * ------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the BSD      *
 * style license a copy of which has been included with this distribution in *
 * the LICENSE.txt file.                                                     *
 *****************************************************************************/
package picounit.features.registry;

import org.picocontainer.MutablePicoContainer;
import org.picocontainer.defaults.AssignabilityRegistrationException;
import org.picocontainer.defaults.DefaultPicoContainer;
import org.picocontainer.defaults.DuplicateComponentKeyRegistrationException;
import org.picocontainer.defaults.InstanceComponentAdapter;
import org.picocontainer.defaults.NotConcreteRegistrationException;

import picounit.PicoUnitException;
import picounit.Registry;
import picounit.features.classloader.ClassReloader;

public class RegistryImpl implements Registry, RegistryEntry, Resolver {
	private final ClassUtil classUtil = new ClassUtil();
	private final PicoContainerInstantiator picoContainerInstantiator;
	private final ClassReloader classReloader;
	private final RegistryEntries registryEntries;
	private final TypeFactory typeFactory;

	private MutablePicoContainer picoContainer;

	public RegistryImpl(ClassReloader classReloader) {
		this(new DefaultPicoContainer(), new RegistryEntries(), classReloader, new DefaultTypeFactory());
	}

	public RegistryImpl(MutablePicoContainer picoContainer, RegistryEntries registryEntries,
		ClassReloader classReloader, TypeFactory typeFactory) {

		this(picoContainer, new PicoContainerInstantiator(picoContainer), registryEntries, classReloader,
			typeFactory);
	}

	public RegistryImpl(MutablePicoContainer picoContainer, PicoContainerInstantiator picoContainerInstantiator,
		RegistryEntries registryEntries, ClassReloader classReloader, TypeFactory typeFactory) {

		this.picoContainerInstantiator = picoContainerInstantiator;
		this.classReloader = classReloader;
		this.picoContainer = SilentlyReplacingMutablePicoContainer.wrap(picoContainer);
		this.registryEntries = registryEntries;
		this.typeFactory = typeFactory;
	}

	public static class PicoContainerInstantiator {
		private final MutablePicoContainer picoContainer;

		public PicoContainerInstantiator(MutablePicoContainer picoContainer) {
			this.picoContainer = picoContainer;
		}

		@SuppressWarnings("unchecked")
		public <T> T instantiate(Class<T> type) {
			try {
				picoContainer.registerComponentImplementation(type);
			}
			catch (DuplicateComponentKeyRegistrationException duplicateComponentKeyRegistrationException) {
				return null;
			}
			catch (NotConcreteRegistrationException notConcreteRegistrationException) {
				return null;
			}

			try {
				return (T) picoContainer.getComponentInstanceOfType(type);
			}
			finally {
				picoContainer.unregisterComponent(type);
			}
		}
	}

	public MutablePicoContainer picoContainer() {
		return picoContainer;
	}

	public void replacePicoContainer(MutablePicoContainer picoContainer) {
		this.picoContainer = SilentlyReplacingMutablePicoContainer.wrap(picoContainer);
	}

	///////////////////////////////////////////////////////////////////////////////////////////////
	// Registry methods
	///////////////////////////////////////////////////////////////////////////////////////////////

	public <B, D extends B> void register(Class<B> type, D instance) {
		type = classReloader.reloadClass(type);

		registryEntries.register(type, instance);
		
		try {
			picoContainer.registerComponentInstance(type, instance);
		}
		catch (AssignabilityRegistrationException assignabilityRegistrationException) {
			picoContainer.registerComponent(new InstanceComponentAdapter(type, instance) {
				@Override
				protected void checkTypeCompatibility() throws AssignabilityRegistrationException {
				}
			});
		}
	}

	public <B, D extends B> void register(Class<B> type, Class<D> implementation) {
		type = classReloader.reloadClass(type);
		implementation = classReloader.reloadClass(implementation);

		registryEntries.register(type, implementation);
		picoContainer.registerComponentImplementation(type, implementation);
	}

	public void register(Class<?> implementation) {
		implementation = classReloader.reloadClass(implementation);

		registryEntries.register(implementation);
		picoContainer.registerComponentImplementation(implementation);
	}

	public void register(String key, boolean value) {
		registryEntries.register(key, value);
		picoContainer.registerComponentInstance(key, value);
	}

	public void register(String key, byte value) {
		registryEntries.register(key, value);
		picoContainer.registerComponentInstance(key, value);
	}

	public void register(String key, char value) {
		registryEntries.register(key, value);
		picoContainer.registerComponentInstance(key, value);
	}

	public void register(String key, double value) {
		registryEntries.register(key, value);
		picoContainer.registerComponentInstance(key, value);
	}

	public void register(String key, float value) {
		registryEntries.register(key, value);
		picoContainer.registerComponentInstance(key, value);
	}

	public void register(String key, int value) {
		registryEntries.register(key, value);
		picoContainer.registerComponentInstance(key, value);
	}

	public void register(String key, long value) {
		registryEntries.register(key, value);
		picoContainer.registerComponentInstance(key, value);
	}

	public void register(String key, short value) {
		registryEntries.register(key, value);
		picoContainer.registerComponentInstance(key, value);
	}

	public void register(String key, String value) {
		registryEntries.register(key, value);
		picoContainer.registerComponentInstance(key, value);
	}

	public <B> ImplementationCapturer<B> registerFactory(Class<B> type) {
		return new DefaultImplementationCapturer<B>();
	}

	public void registerWith(Registry registry) {
		registryEntries.registerWith(registry);
	}

	///////////////////////////////////////////////////////////////////////////////////////////////
	// Resolver methods
	///////////////////////////////////////////////////////////////////////////////////////////////

	@SuppressWarnings("unchecked")
	public <T> T get(String name, Class<T> type) {
//		System.out.println("type: " + type + ", classLoader: " + type.getClassLoader());

		if (type.equals(MutablePicoContainer.class)) {
			return (T) picoContainer; 
		}

		if (typeFactory.hasInstance(type)) {
			return typeFactory.getInstance(type);
		}

		if (classUtil.isPrimative(type)) {
			return (T) picoContainer.getComponentInstance(name);
		}

		T instance = (T) picoContainer.getComponentInstanceOfType(type);

		if (instance == null) {
			instance = picoContainerInstantiator.instantiate(type);
		}

		if (instance == null) {
			throw new PicoUnitException("Could not find fixture of type: " + type.getName());
		}

		return instance;
	}

	// This is the number of matching implementations of parameters required in the constructor, used for
	// data driven tests
	public <T> int numMatching(String name, Class<T> type) {
		return 1;
	}
}
